﻿#ifndef MODBUSRTU_H
#define MODBUSRTU_H

#include <QObject>
#include <QWidget>
#include <QListWidget>
#include <QDateTime>
#include <QApplication>
#include <QDebug>
#include <QFile>

#include <iostream>
#include <vector>

using namespace std;

#include "serialport.h"

#define u8 uint8_t
#define u16 uint16_t

class ModbusRTU : public SerialPort
{
public:
    ModbusRTU(QListWidget *listwidget = 0);

public:
    /* modbus rtu 03 读寄存器命令组帧函数
     * cmd 为命令地址
     * addr 为要读取的寄存器地址
     * len 为读取的寄存器长度
     * 返回值：空
     */
    void ModbusRTU_03(QByteArray &cmd, uint8_t *addr, uint16_t len) //03命令组帧
    {
        uint16_t crc;

        cmd[0] = modbusAddr;
        cmd[1] = 0x03;
        cmd[2] = addr[0];
        cmd[3] = addr[1];
        cmd[4] = len>>8;
        cmd[5] = len;
        crc = GetCRCModBus((uint8_t *)cmd.data(), 6);
        cmd[6] = crc;
        cmd[7] = crc>>8;
    }
    /*
     * modbus rtu 10 写多个寄存器组帧函数
     * cmd为命令地址
     * addr为要写的寄存器地址
     * len为要写的寄存器长度
     * data为写入的数据
     * 返回值：空
     */
    void ModbusRTU_10(QByteArray &cmd, uint8_t *addr, uint16_t len, uint8_t *data) //10命令组帧
    {
        uint16_t crc;

        cmd[0] = modbusAddr;
        cmd[1] = 0x10;
        cmd[2] = addr[0];
        cmd[3] = addr[1];
        cmd[4] = len>>8;
        cmd[5] = len;
        cmd[6] = len*2;

        for(int i=0;i<len*2;i++)
        {
            cmd[7+i] = data[i];
        }

        crc = GetCRCModBus((uint8_t *)cmd.data(), 7+len*2);
        cmd[7+len*2] = crc;
        cmd[7+len*2+1] = crc>>8;
    }
    /*
     * modbus 发送函数
     * port 串口
     * snd 发送的命令帧
     * rcv 收到的数据帧
     * 返回值：正确返回true，反之返回false
     */
    bool ModbusRTUSend(QSerialPort *port, QByteArray snd, QByteArray &rcv)  //发送命令
    {   
        if(port == nullptr)   // 健壮机制，检查串口是否正常
            return false;
        port->clear();

        //阻塞法 频繁读取时卡顿明显
//        port->write(snd);
//        QApplication::processEvents();
//        if(port->waitForBytesWritten(responDelayTime)){
//            ShowSndMessage(snd);
//            QApplication::processEvents();
//            if(port->waitForReadyRead(responDelayTime)){
//                rcv = port->readAll();
//                QApplication::processEvents();
//                while(port->waitForReadyRead(40))
//                    rcv += port->readAll();
//                if(rcv.isEmpty())
//                    return false;
//                ShowRcvMessage(rcv);
//            }
//        }

        //挂起GUI等待读取
        port->write(snd);
        ShowSndMessage(snd);

        MyDelay(responDelayTime);

        rcv = port->readAll();
        if(rcv.isEmpty())
            return false;
        ShowRcvMessage(rcv);

        //功能码
        if(snd[1] != rcv[1])
            return false;
//        if(rcv[1] == 0x3 && snd[3]*0x100)

        //CRC
        return CheckCRCModbus((uint8_t *)rcv.data(), rcv.size());
    }
    /*
     * modbus 03命令返回的值
     * port 串口
     * addr 寄存器地址
     * len  读取的寄存器长度
     * value 保存得到的值
     */
    void ModbusRTU_03_GetValue(QSerialPort *port, uint8_t *addr, uint16_t len, vector<uint8_t> &value)
    {
        QByteArray snd, rcv;
        ModbusRTU_03(snd, addr, len);
        ModbusRTUSend(port, snd, rcv);

        while(value.size())
            value.pop_back();

        int size = (uint8_t)rcv[2];

        for(int i=0;i<size;i++)
            value.push_back((uint8_t)rcv[3+i]);
    }

    // modbus响应延时时间
    void changeResponDelayTime(int nums)
    {
        responDelayTime = nums;
    }

    /*
     * 自动编写通讯报文，用于测试继电器及多机通讯时使用
     * nums 连接在485总线的机器数量
     */
    QByteArray AutoWriteModbus(int nums, QByteArray cmd)
    {
        //自动编写报文
//        QFile file("./out.txt");
//        if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
//            return;

//        QTextStream out(&file);
//        cmd.clear();

//        cmd[0] = 0x00,
        cmd[1] = 0x03,
        cmd[2] = nums*2;

        qint64 t = QDateTime::currentMSecsSinceEpoch();

        qsrand(t);
        for(int j=0;j<nums;j++)
        {
            cmd[3+j*2] = 0;
            cmd[4+j*2] = qrand()%12;
        }

        uint16_t crc = GetCRCModBus((uint8_t *)cmd.data(), nums*2+3);
        cmd[nums*2+3] = crc%256;
        cmd[nums*2+4] = crc/256;
//        QString str = sp.ShowHex(cmd);
//        out << str << endl;

        return cmd;
    }
    /*
     *  修改Modbus通讯地址
     */
    void ChangeModbusAddr(uint8_t newAddr)
    {
        modbusAddr = newAddr;
    }

private:
    void ShowSndMessage(QByteArray snd)
    {
        QString cmd = sp.ShowHex(snd);
        MessageListWidget->addItem(QDateTime::currentDateTime().toString("[yyyy-MM-dd hh:mm:ss] :")
                                   + cmd);
        MessageListWidget->item(MessageListWidget->count()-1)->setTextColor(Qt::blue);
        MessageListWidget->setCurrentRow(MessageListWidget->count()-1);
    }
    void ShowRcvMessage(QByteArray rcv)
    {
        QString cmd = sp.ShowHex(rcv);
        MessageListWidget->addItem(QDateTime::currentDateTime().toString("[yyyy-MM-dd hh:mm:ss] :")
                                   + cmd);
        MessageListWidget->item(MessageListWidget->count()-1)->setTextColor(Qt::red);
        MessageListWidget->setCurrentRow(MessageListWidget->count()-1);
    }
    void MyDelay(int msecs) //ms延时
    {
        qint64 n = QDateTime::currentMSecsSinceEpoch();
        qint64 now;
        do{
            now = QDateTime::currentMSecsSinceEpoch();
            QApplication::processEvents();
        } while (now - n < msecs);//
    }

    u16 CalcCRCModBus(u8 cDataIn, u16 wCRCIn)
    {
        u16 wCheck = 0;
        u8 i;
        wCRCIn = wCRCIn ^ cDataIn;

        for( i = 0; i < 8; i++)
        {
            wCheck = wCRCIn & 1;
            wCRCIn = wCRCIn >> 1;
            wCRCIn = wCRCIn & 0x7fff;

            if(wCheck == 1)
            {
                wCRCIn = wCRCIn ^ 0xa001;
            }
            wCRCIn = wCRCIn & 0xffff;
        }

        return wCRCIn;
    }
public:
    u16 GetCRCModBus(u8 *pDataIn, u8 iLenIn)
    {
        u16 wHi = 0;
        u16 wLo = 0;
        u16 wCRC;
        u8 i;
        wCRC = 0xFFFF;

        for ( i = 0; i < iLenIn; i++)
        {
            wCRC = CalcCRCModBus(*pDataIn, wCRC);
            pDataIn++;
        }

        wHi = wCRC / 256;
        wLo = wCRC % 256;
        wCRC = (wHi << 8) | wLo;

        return(wCRC);
    }
    bool CheckCRCModbus(u8 *pDataIn, u8 iLenIn)
    {
        u16 wHi = 0;
        u16 wLo = 0;
        u16 wCRC;
        u8 i;
        wCRC = 0xFFFF;
        u8 *temp = pDataIn;

        for ( i = 0; i < iLenIn-2; i++)
        {
            wCRC = CalcCRCModBus(*pDataIn, wCRC);
            pDataIn++;
        }

        wHi = wCRC / 256;
        wLo = wCRC % 256;

        if(wLo == temp[iLenIn-2] && wHi == temp[iLenIn-1])
            return true;
        return false;
    }

private:
    QListWidget *MessageListWidget;
    SerialPort sp;
    int responDelayTime;
    uint8_t modbusAddr;
};

#endif // MODBUSRTU_H
